﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Eventbrite.LinqProvider.Translators
{
    public class WhereTranslator: Helpers.ExpressionVisitor
    {
        private static Dictionary<string, int> _formats = new Dictionary<string, int>{{"Conference", 1}, {"Seminar", 2 }, {"Expo", 3}, {"Convention", 4}, {"Festival", 5},{"Performance", 6},{"Screening", 7}, {"Gala", 8}, {"Class", 9}, {"Networking", 10}, 
        {"Party", 11 } ,{"Rally", 12}, {"Tournament", 13}, {"Game", 14}, {"Race", 15}, {"Tour", 16}, {"Attraction", 17}, {"Retreat", 18}, {"Appearance", 19}, {"Other", 20} };
        
        private static Dictionary<string,int> _categories = new Dictionary<string,int>{{"Business", 101}, {"ScienceTech",102 }, {"Music", 103}, {"FilmMedia", 104}, {"Arts", 105},{"FashionBeauty", 106},{"HealthWellness", 107}, {"SportsFitness", 108}, {"TravelOutdoor", 109}, {"FoodDrink", 110}, 
        {"CharityCauses", 111 } ,{"Government", 112}, {"Community", 113}, {"ReligionSpirituality", 114}, {"FamilyEducation", 115}, {"SeasonalHoliday", 116}, {"HomeLifestyle", 117}, {"AutoBoatAir", 118}, {"Hobbies", 119}, {"Other", 120} };
        private const string dateFormate = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";

        private static Dictionary<string, string> _enumLookup = new Dictionary<string, string>() {{"ThisMonth", "this_month"}, 
                                                                                                   {"ThisWeek", "this_week"}, {"ThisWeekend", "this_weekend"},
                                                                                                    {"NextWeek", "next_week"}, {"Today", "today" }, {"Tomorrow", "tomorrow"} };
        private Dictionary<string, object> _nameLookup = new Dictionary<string, object>();
        private static Dictionary<string, string> _paramLookup = new Dictionary<string, string>(){{"datemodifiedkeyword", "date_modified.keyword"}, {"datecreatedkeyword", "date_created.keyword" }, {"date","start_date" }, {"datekeyword", "start_date.keyword" }, {"user", "user.id"}, {"organizer", "organizer.id"}, {"within", "location.within"}, {"longitude", "location.longitude"}, {"latitude","location.latitude"}, {"address","location.address"}, {"city", "venue.city"}, {"region", "venue.region"}, {"country", "venue.country"}, {"postalcode", "postal_code"},
        {"withinunit", "within_unit"}, {"datecreated", "date_created"}, {"datemodified", "date_modified"}, {"sinceid", "since_id"}};
        
        StringBuilder _sb = new StringBuilder();

        public WhereTranslator() { }

        public void Translate(LambdaExpression expression)
        {
            base.VisitLambda(expression);
        }

        public string GetRequestParams()
        {
            return _sb.ToString();
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            Visit(b.Left);
            switch (b.NodeType)
            {
                case ExpressionType.And :
                    _sb.Append("&");
                    break;
                case ExpressionType.Equal:
                    if(_sb.ToString() != "start_date")
                        _sb.Append("=");
                    break;
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                    break;
                default:
                    throw new NotSupportedException(
                      string.Format(
                        "The Where predicate does not support '{0}' binary operator",
                        b.NodeType));
            }
            if (b.Right.NodeType == ExpressionType.Constant && b.Right.Type == typeof(DateTime?))
            {
                 VisitConstantDate((ConstantExpression)b.Right, b.NodeType);
            }
            else
            {
                Visit(b.Right);
            }
            return b;
        }

        protected Expression VisitConstantDate(ConstantExpression c, ExpressionType operatorType)
        {
            var start = ".range_start={0}";
            var end = ".range_end={0}";
            var val = ((DateTime?)c.Value).Value.ToUniversalTime().ToString(dateFormate);
            switch (operatorType)
            {
                case ExpressionType.LessThan:
                    var lessOneDay = ((DateTime?)c.Value).Value.AddDays(-1).ToUniversalTime();
                    val = lessOneDay.ToString(dateFormate);
                    _sb.Append(string.Format(end, val));
                    break;
                case ExpressionType.GreaterThan:
                    var moreOneDay = ((DateTime?)c.Value).Value.AddDays(1).ToUniversalTime();
                    _sb.Append(string.Format(start, moreOneDay.ToString(dateFormate)));
                    break;
                case ExpressionType.Equal:
                case ExpressionType.GreaterThanOrEqual:
                    _sb.Append(string.Format(start, val));
                    break;
                case ExpressionType.LessThanOrEqual:
                    _sb.Append(string.Format(end, val));
                    break;
                default:
                    throw new NotSupportedException( string.Format("{0} operator is not supported", operatorType));
            }
            return c;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
           if(c.Value is Int32 && EnumType != null)
            {
                var val = GetEnumName(c.Value);
                _sb.Append(val);
                EnumType = null;
             }
            else
            {
                _sb.Append(c.Value);
            }
            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            var request = m.Expression as MemberExpression;
            if (request == null || request.Member.Name != "Request")
                throw new NotSupportedException();

            if (m.Type.BaseType.Name == "Enum")
                EnumType = m.Type;

            var paramName = m.Member.Name.ToLower();
            

            if ( _paramLookup.ContainsKey(paramName))
                paramName = _paramLookup[paramName];

            if (paramName != "start_date" && paramName != "date_created" && paramName != "date_modified")
            {
                if (_nameLookup.ContainsKey(paramName))
                    throw new InvalidOperationException(String.Format("Request url can have only one {0} param", paramName));
                else
                    _nameLookup.Add(paramName, null);
            }
            
            _sb.Append(paramName);
            return m;
        }

        private string GetEnumName(object val)
        {
            var name = Enum.GetName(EnumType, val);
            if (name == null)
               return  ProcessFlags(val);
            if (_categories.ContainsKey(name))
                return _categories[name].ToString();
            if (_formats.ContainsKey(name))
                return _formats[name].ToString();
            if(_enumLookup.ContainsKey(name))
                return _enumLookup[name];
            return name ; 
        }

        private string ProcessFlags(object val)
        {
            var values = Enum.Format(EnumType, val, "G");
            var names = values.Split(',');
            string result = string.Empty;
            if (EnumType.Name == "Categories" )
            {
                foreach (var c in names)
                {
                    if (!String.IsNullOrEmpty(result))
                        result += ",";
                    result += _categories[c.Trim()];
                }
            }
            else if( EnumType.Name == "Formats")
            {
                foreach (var c in names)
                {
                    if (!String.IsNullOrEmpty(result))
                        result += ",";
                    result += _formats[c.Trim()];
                }
            }
            else
            {
                foreach (var c in names)
                {
                    if (!String.IsNullOrEmpty(result))
                        result += ",";
                    result += c;
                }
            }
            return result;
        }

             
        private bool ValidateRangeOperator(ExpressionType type)
        {
            switch (type)
            {
                case(ExpressionType.GreaterThan):
                case(ExpressionType.GreaterThanOrEqual):
                case(ExpressionType.LessThan):
                case(ExpressionType.LessThanOrEqual):
                    return true;
                default:
                    return false;
            }
        }
       
        private DateTime Offset(DateTime val, ExpressionType type)
        {
            switch (type)
            {
                case(ExpressionType.LessThan):
                    return val.AddDays(-1);
                case(ExpressionType.GreaterThan):
                    return val.AddDays(1);
                default:
                    return val;
            }
        }
        
        private Type EnumType { get; set; }
       
        
    }
}
